package com.minio.learning.springminio.service.impl;

import com.minio.learning.springminio.entity.FileInfo;
import com.minio.learning.springminio.entity.FileVO;
import com.minio.learning.springminio.entity.RemoveFailVO;
import com.minio.learning.springminio.exception.ServiceException;
import com.minio.learning.springminio.service.MinioService;
import io.minio.*;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author YanXxx
 * @date 2022-12-01 17:30
 */
@Service
@Slf4j
public class MinioServiceImpl implements MinioService {

    @Resource
    private MinioClient minioClient;

    @Value("${minio.storagePolicy}")
    private String storagePolicy;

    @Value("${minio.endpoint}")
    private String endpoint;

    /**
     * 创建一个桶
     *
     * @param bucketName bucketName
     */
    @Override
    public boolean createBucket(String bucketName) {
        boolean found = bucketIsExist(bucketName);
        try {
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
        } catch (Exception e) {
            log.error("minio创建一个桶失败：{}", e.getMessage());
            throw new ServiceException("创建存储桶失败！");
        }
        return true;
    }

    /**
     * 获取文件上传前缀
     *
     * @return String
     */
    private String getFilePrefix() {
        String prefix = null;
        LocalDateTime dateTime = LocalDateTime.now();
        switch (storagePolicy) {
            case "byDate":
                DateTimeFormatter byDate = DateTimeFormatter.ofPattern("yyyy/MM/dd/");
                prefix = dateTime.format(byDate);
                break;
            case "byMonth":
                DateTimeFormatter byMonth = DateTimeFormatter.ofPattern("yyyy/MM/");
                prefix = dateTime.format(byMonth);
                break;
            case "byYear":
                prefix = dateTime.getYear() + "/";
                break;
            default:
                prefix="";
                break;
        }
        return prefix;
    }

    /**
     * 上传一个文件
     *
     * @param bucketName bucketName
     * @param files      files
     */
    @Override
    public List<FileVO> uploadFile(String bucketName, MultipartFile[] files) {
        int length = files.length;
        boolean result = bucketIsExist(bucketName);
        if (!result) {
            throw new ServiceException("存储桶不存在!");
        }
        List<FileVO> fileList = new ArrayList<>(length);
        for (MultipartFile file : files) {
            String uploadFileName = file.getOriginalFilename();
            try {
                PutObjectArgs args = PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(getFilePrefix() + uploadFileName)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build();
                minioClient.putObject(args);

                String url = endpoint + "/" + bucketName + "/" + getFilePrefix() + uploadFileName;

                FileVO fileVO = new FileVO();
                fileVO.setFileName(uploadFileName);
                fileVO.setUrl(url);
                fileList.add(fileVO);
            } catch (Exception e) {
                log.error("minio上传文件失败：{}", e.getMessage());
            }
        }
        return fileList;
    }

    /**
     * 列出所有的桶
     */
    @Override
    public List<String> listBuckets() {
        List<Bucket> list;
        try {
            list = minioClient.listBuckets();
        } catch (Exception e) {
            throw new ServiceException("列出所有的桶失败！");
        }
        List<String> names = new ArrayList<>();
        list.forEach(b -> names.add(b.name()));
        return names;
    }

    /**
     * 判断存储桶是否存在
     *
     * @param bucket bucket
     * @return boolean
     */
    private boolean bucketIsExist(String bucket) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        } catch (Exception e) {
            throw new ServiceException("系统中断，稍后重试！");
        }
    }

    /**
     * 列出一个桶中的所有文件和目录
     *
     * @param bucketName bucketName
     */
    @Override
    public List<FileInfo> listFiles(String bucketName, String prefix) {
        boolean result = bucketIsExist(bucketName);
        if (!result) {
            throw new ServiceException("存储桶不存在!");
        }
        Iterable<Result<Item>> results;
        if (StringUtils.isNotBlank(prefix)) {
            results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(false).build());
        } else {
            results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).recursive(false).build());
        }

        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        List<FileInfo> infos = new ArrayList<>();
        results.forEach(r -> {
            FileInfo info = new FileInfo();
            try {
                Item item = r.get();
                info.setFilename(item.objectName());
                boolean dir = item.isDir();
                info.setDirectory(dir);
                // 文件夹没有修改时间
                if (!dir) {
                    info.setLastModifiedTime(item.lastModified().format(fmt));
                }
                infos.add(info);
            } catch (Exception e) {
                log.info("获取文件失败！{}", e.getMessage());
            }
        });
        return infos;
    }

    /**
     * 下载一个文件
     *
     * @param bucketName bucketName
     * @param filePath filePath
     */
    @Override
    public void download(String bucketName, String filePath, HttpServletResponse response) {
        try {
            String fileName = getFileName(filePath, bucketName);
            // 获取对象信息
            StatObjectResponse statObjectResponse = minioClient.statObject(
                    StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
            response.setContentType(statObjectResponse.contentType());
            response.setHeader("Content-Disposition", URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()));

            InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            IOUtils.copy(stream, response.getOutputStream());
        } catch (Exception e) {
            throw new ServiceException("下载失败!");
        }

    }

    /**
     * 删除一个桶
     *
     * @param bucketName bucketName
     */
    @Override
    public boolean deleteBucket(String bucketName) {
        boolean result = bucketIsExist(bucketName);
        if (!result) {
            throw new ServiceException("存储桶不存在!");
        }
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error("minio删除存储桶失败：{}", e.getMessage());
            throw new ServiceException("删除存储桶失败!");
        }
        return true;
    }

    /**
     * 删除一个对象
     *
     * @param bucketName bucketName
     * @param filePath   filePath
     * @return 删除结果
     */
    @Override
    public boolean deleteObject(String bucketName, String filePath) {
        String fileName = getFileName(bucketName, filePath);
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            log.error("minio删除文件失败：{}", e.getMessage());
            throw new ServiceException("删除文件失败!");
        }
        return true;
    }

    /**
     * 获取文件名称
     *
     * @param filePath   文件路径
     * @param bucketName 存储桶名称
     * @return 获取文件名称
     */
    private String getFileName(String filePath, String bucketName) {
        String prefix = endpoint + "/" + bucketName + "/";
        int length = prefix.length();
        return filePath.substring(length);
    }

    /**
     * 删除一个或多个对象
     * 删除文件夹下文件 /fileName/fileName/sss.txt
     *
     * @param bucketName bucketName
     * @param filePaths  文件路径
     * @return 删除失败列表
     */
    @Override
    public List<RemoveFailVO> deleteObjects(String bucketName, List<String> filePaths) {
        List<DeleteObject> list = new ArrayList<>();
        for (String filePath : filePaths) {
            String fileName = getFileName(filePath,bucketName);
            list.add(new DeleteObject(fileName));
        }
        Iterable<Result<DeleteError>> iterable = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build());
        List<RemoveFailVO> failList = new ArrayList<>();
        for (Result<DeleteError> result : iterable) {
            DeleteError error = null;
            try {
                error = result.get();
            } catch (Exception e) {
                log.error("minio删除对象失败：{}", e.getMessage());
            }
            assert error != null;
            log.info("minio删除错误->bucketName={},objectName={},message={}", error.bucketName(), error.objectName(), error.message());
            RemoveFailVO failVO = new RemoveFailVO();
            failVO.setObjectName(error.objectName());
            failVO.setBucketName(error.bucketName());
            failList.add(failVO);
        }
        return failList;
    }
}
